﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using KaroEngine.Exceptions;

namespace KaroEngine
{
    public static class Rules
    {
        public static short initialBoardWidth = 5;
        public static short initialBoardHeight = 4;

        // PositionFrom -1 means it is the starting phase of the game! (it IS considered valid)
        public static bool IsValidMove(Board currentState, Move move, bool canThrowExceptions)
        {
            if (move.PositionFrom == -1 && move.PositionTo == -1 && move.TilePositionToMove == -1)
                return true;

            return currentState.NumberOfConesOnBoard == 12 ?
                IsValidRegularMove( currentState, move, canThrowExceptions ) :
                IsValidPlacingMove( currentState, move, canThrowExceptions );
        }

        private static bool IsValidPlacingMove(Board currentState, Move move, bool canThrowExceptions)
        {
            // If positionTo is at position without Tile present
            if (move.PositionTo >= 0 && currentState.Tiles[move.PositionTo] == null)
            {
                if (canThrowExceptions)
                {
                    MoveToException e = new MoveToException();
                    throw e;
                }

                return false;
            }

            // If the positionTo is not NULL (there is a tile) and the positionTo is already occupied (not Empty)
            if (currentState.Tiles[move.PositionTo] != null && currentState.Tiles[move.PositionTo].State != TileState.Empty)
            {
                if (canThrowExceptions)
                {
                    TileOccupiedException e = new TileOccupiedException();
                    throw e;
                }

                return false;
            }

            return true;
        }

        private static bool IsValidRegularMove(Board currentState, Move move, bool canThrowExceptions)
        {
            // If positionTo is at position without Tile present (and we don't use an extra tile)
            if (move.PositionTo >= 0 && currentState.Tiles[move.PositionTo] == null && move.TilePositionToMove == -1)
            {
                if (canThrowExceptions)
                {
                    MoveToException e = new MoveToException(move);
                    throw e;
                }

                return false;
            }

            // If White on turn && positionFrom is not White (i.e. trying to move opponents piece), same for Red
            // NOTE-1 (see below)
            //  >> If positionFrom == null, 'wrong player' exception..(player can only pick his/her own tiles to move)
            //  >> GUI should take care of this exception.
            if (currentState.Tiles[move.PositionFrom] == null ||    // NOTE-1
                (currentState.CurrentPlayer == Player.White &&
                !(currentState.Tiles[move.PositionFrom].State == TileState.WhiteNormal ||
                    currentState.Tiles[move.PositionFrom].State == TileState.WhiteUpsideDown)) ||
                (currentState.CurrentPlayer == Player.Red &&
                !(currentState.Tiles[move.PositionFrom].State == TileState.RedNormal ||
                    currentState.Tiles[move.PositionFrom].State == TileState.RedUpsideDown)))
            {
                if (canThrowExceptions)
                {
                    InvalidPlayerException e = new InvalidPlayerException(move);
                    throw e;
                }

                return false;
            }

            // If the positionTo is not NULL (there is a tile) and the positionTo is already occupied (not Empty)
            if (currentState.Tiles[move.PositionTo] != null && currentState.Tiles[move.PositionTo].State != TileState.Empty)
            {
                if (canThrowExceptions)
                {
                    TileOccupiedException e = new TileOccupiedException(move);
                    throw e;
                }

                return false;
            }

            // If we want to make a move not to our neighbour or not over our neighbour
            bool isPositionToInInnerList = false;
            bool isPositionToInOuterList = false;

            Neighbours neighbours = currentState.GetAdjacentIndices(move.PositionFrom);
            List<short> innerIndices = neighbours.InnerNeighbours;    // Innerlist
            foreach (short s in innerIndices)
            {
                if (s != move.PositionTo) continue;
                isPositionToInInnerList = true;
                break;
            }

            List<short> outerIndices = neighbours.OuterNeighbours;    // Outerlist
            foreach (short s in outerIndices)
            {
                if (s != move.PositionTo) continue;
                isPositionToInOuterList = true;
                break;
            }
            if (!isPositionToInInnerList && !isPositionToInOuterList)
            {
                if (canThrowExceptions)
                {
                    InvalidJumpException e = new InvalidJumpException(move);
                    throw e;
                }

                return false;
            }



            // If TilePositionToMove cannot be placed at positionTo (no adjacent tile or already a tile present)
            if (move.TilePositionToMove != -1)
            {
                if (currentState.Tiles[move.PositionTo] != null /*|| currentBoard.Tiles[move.PositionTo].State != TileState.Empty)*/ ||
                 (GetRequiredNeighbours(currentState, move.PositionTo, move.TilePositionToMove).Count <= 0))
                {
                    if (canThrowExceptions)
                    {
                        InvalidTilePositionException e = new InvalidTilePositionException(move);
                        throw e;
                    }

                    return false;
                }
            }

            // If an extra tile is used
            if (move.TilePositionToMove != -1)
            {
                // If TilePositionToMove is not empty
                if (currentState.Tiles[move.TilePositionToMove] != null)
                {
                    // If TilePositionToMove cannot be taken because it is occupied
                    if (currentState.Tiles[move.TilePositionToMove].State != TileState.Empty)
                    {
                        if (canThrowExceptions)
                        {
                            NotAnEmptyTileException e = new NotAnEmptyTileException(move);
                            throw e;
                        }

                        return false;
                    }

                    // If TilePositionToMove cannot be taken because it doesnt have enough free sides
                    if (!currentState.IsCornerTile(move.TilePositionToMove))
                    {
                        if (canThrowExceptions)
                        {
                            LockedTileException e = new LockedTileException(move);
                            throw e;
                        }

                        return false;
                    }
                }
                else
                {
                    // The GUI should make sure one cannot pick a 'null' tile to move (except for AI moves)
                    if (canThrowExceptions)
                    {
                        NotAnEmptyTileException e = new NotAnEmptyTileException(move);
                        throw e;
                    }

                    return false;
                }
            }

            return true;
        }

        // Get neighbours right above, left, right, below.
        // Used for placing a tile (there has to be a tile at one of these positions)
        static List<short> GetRequiredNeighbours(Board board, short positionTo, short tilePositionToMove)
        {
            List<short> requiredNeighbours = new List<short>();

            /**
             * Check if positionTo is in range of board
             * Check if positionTo has a tile on it's right/left/top/bottom
             */
            if ( positionTo < Engine.width * Engine.height && board.Tiles[ positionTo + 1 ] != null )
                requiredNeighbours.Add( (short)( positionTo + 1 ) );
            if ( positionTo - 1 >= 0 && board.Tiles[ positionTo - 1 ] != null )
                requiredNeighbours.Add( (short)( positionTo - 1 ) );
            if ( positionTo + Engine.width < Engine.width * Engine.height && board.Tiles[ positionTo + Engine.width ] != null )
                requiredNeighbours.Add( (short)( positionTo + Engine.width ) );
            if ( positionTo - Engine.width >= 0 && board.Tiles[ positionTo - Engine.width ] != null )
                requiredNeighbours.Add( (short)( positionTo - Engine.width ) );

            // If there is only 1 adjacent tile present and that is the tile we use to move to
            if ( requiredNeighbours.Count == 1 && requiredNeighbours[ 0 ] == tilePositionToMove )
                return new List<short>();

            return requiredNeighbours;
        }

        // Assumption that the positionTo is a valid position (not outside screen..)
        static bool CanMoveTileTo(short positionFrom, short positionTo)
        {
            if ((positionFrom - 1 == positionTo) ||
                (positionFrom + 1 == positionTo) ||
                (positionFrom - Engine.width == positionTo) ||
                (positionFrom + Engine.width == positionTo))
                return true;

            return false;
        }
    }
}